C.S.Burner 🪙 ━►🔥GIT Node
Node / reticulum_mirror / MeshChatX / files / meshchatx / src / frontend / components / debug / DebugLogsPage.vue
Displaying Raw • Download
meshchatx/src/frontend/components/debug/DebugLogsPage.vue 65ca9ddc823fcc2c8f8653c553b944b85db0875f (65ca9ddc) Text, 24.14 KB
T8b949e<!-- SPDX-License-Identifier: 0BSD -->
Tff7b72<templateTff7b72>
Tff7b72<div Te6edf3class=Ta5d6ff"Ta5d6ffflex flex-col flex-1 overflow-hidden min-w-0 bg-slate-50 dark:bg-zinc-950Ta5d6ff"Tff7b72>
Tff7b72<ToolsPageHeader Te6edf3icon=Ta5d6ff"Ta5d6ffconsoleTa5d6ff" Te6edf3:title=Ta5d6ff"Te6edf3$tTa5d6ff(Ta5d6ff'Ta5d6ffdebug.titleTa5d6ff'Ta5d6ff)Ta5d6ff" Te6edf3:description=Ta5d6ff"Te6edf3$tTa5d6ff(Ta5d6ff'Ta5d6ffdebug.descriptionTa5d6ff'Ta5d6ff)Ta5d6ff" Te6edf3accent=Ta5d6ff"Ta5d6ffzincTa5d6ff"Tff7b72>
Tff7b72<template #actionsTff7b72>
Tff7b72<button Te6edf3type=Ta5d6ff"Ta5d6ffbuttonTa5d6ff" Te6edf3class=Ta5d6ff"Ta5d6ffsecondary-chip px-4 py-2 text-smTa5d6ff" @Te6edf3click=Ta5d6ff"Ta5d6ffrefreshActiveTa5d6ff"Tff7b72>
Tff7b72<MaterialDesignIcon Te6edf3icon-name=Ta5d6ff"Ta5d6ffrefreshTa5d6ff" Te6edf3class=Ta5d6ff"Ta5d6ffw-4 h-4Ta5d6ff" Tff7b72/>
Refresh
Tff7b72</button>
Tff7b72<button Te6edf3type=Ta5d6ff"Ta5d6ffbuttonTa5d6ff" Te6edf3class=Ta5d6ff"Ta5d6ffprimary-chip px-4 py-2 text-smTa5d6ff" @Te6edf3click=Ta5d6ff"Ta5d6ffcopyActiveTa5d6ff"Tff7b72>
Tff7b72<MaterialDesignIcon Te6edf3icon-name=Ta5d6ff"Ta5d6ffcontent-copyTa5d6ff" Te6edf3class=Ta5d6ff"Ta5d6ffw-4 h-4Ta5d6ff" Tff7b72/>
Copy All
Tff7b72</button>
Tff7b72</template>
Tff7b72</ToolsPageHeader>
Tff7b72<div
Te6edf3class=Ta5d6ff"Ta5d6ffflex-1 overflow-y-auto flex flex-col w-full px-3 sm:px-4 md:px-5 lg:px-8 py-3 sm:py-6 pb-[max(1.5rem,env(safe-area-inset-bottom))]Ta5d6ff"
Tff7b72>
Tff7b72<div Te6edf3class=Ta5d6ff"Ta5d6ffflex flex-col mb-4 w-full max-w-6xl mx-auto space-y-4 min-w-0Ta5d6ff"Tff7b72>
Tff7b72<div
Te6edf3class=Ta5d6ff"Ta5d6ffflex flex-nowrap sm:flex-wrap gap-2 border-b border-gray-200 dark:border-zinc-700 pb-2 overflow-x-auto overscroll-x-contain -mx-4 px-4 sm:mx-0 sm:px-0 no-scrollbarTa5d6ff"
Tff7b72>
Tff7b72<button
Te6edf3type=Ta5d6ff"Ta5d6ffbuttonTa5d6ff"
Te6edf3class=Ta5d6ff"Ta5d6ffshrink-0 px-4 py-2 text-sm rounded-md transition-colorsTa5d6ff"
Te6edf3:class=Ta5d6ff"
Ta5d6ff activeTab === Ta5d6ff'Ta5d6fflogsTa5d6ff'
Ta5d6ff ? Ta5d6ff'Ta5d6ffbg-blue-600 text-whiteTa5d6ff'
Ta5d6ff : Ta5d6ff'Ta5d6ffbg-gray-100 dark:bg-zinc-800 text-gray-700 dark:text-gray-300 hover:bg-gray-200 dark:hover:bg-zinc-700Ta5d6ff'
Ta5d6ff Ta5d6ff"
@Te6edf3click=Ta5d6ff"Ta5d6ffswitchTab(Ta5d6ff'Ta5d6fflogsTa5d6ff'Ta5d6ff)Ta5d6ff"
Tff7b72>
{{ Te6edf3$t("debug.tab_logs") }}
Tff7b72</button>
Tff7b72<button
Te6edf3type=Ta5d6ff"Ta5d6ffbuttonTa5d6ff"
Te6edf3class=Ta5d6ff"Ta5d6ffshrink-0 px-4 py-2 text-sm rounded-md transition-colorsTa5d6ff"
Te6edf3:class=Ta5d6ff"
Ta5d6ff activeTab === Ta5d6ff'Ta5d6ffaccessTa5d6ff'
Ta5d6ff ? Ta5d6ff'Ta5d6ffbg-blue-600 text-whiteTa5d6ff'
Ta5d6ff : Ta5d6ff'Ta5d6ffbg-gray-100 dark:bg-zinc-800 text-gray-700 dark:text-gray-300 hover:bg-gray-200 dark:hover:bg-zinc-700Ta5d6ff'
Ta5d6ff Ta5d6ff"
@Te6edf3click=Ta5d6ff"Ta5d6ffswitchTab(Ta5d6ff'Ta5d6ffaccessTa5d6ff'Ta5d6ff)Ta5d6ff"
Tff7b72>
{{ Te6edf3$t("debug.tab_access_attempts") }}
Tff7b72</button>
Tff7b72</div>
Tff7b72<div
Te6edf3v-if=Ta5d6ff"Ta5d6ffactiveTab === Ta5d6ff'Ta5d6fflogsTa5d6ff'Ta5d6ff"
Te6edf3class=Ta5d6ff"Ta5d6ffflex flex-wrap gap-3 items-center bg-white/50 dark:bg-zinc-800/50 p-3 rounded-lg border border-gray-200 dark:border-zinc-700Ta5d6ff"
Tff7b72>
Tff7b72<div Te6edf3class=Ta5d6ff"Ta5d6ffrelative flex-1 min-w-[200px]Ta5d6ff"Tff7b72>
Tff7b72<div Te6edf3class=Ta5d6ff"Ta5d6ffabsolute inset-y-0 left-0 pl-3 flex items-center pointer-events-noneTa5d6ff"Tff7b72>
Tff7b72<MaterialDesignIcon Te6edf3icon-name=Ta5d6ff"Ta5d6ffmagnifyTa5d6ff" Te6edf3class=Ta5d6ff"Ta5d6ffw-4 h-4 text-gray-400Ta5d6ff" Tff7b72/>
Tff7b72</div>
Tff7b72<input
Te6edf3v-model=Ta5d6ff"Ta5d6ffsearchTa5d6ff"
Te6edf3type=Ta5d6ff"Ta5d6fftextTa5d6ff"
Te6edf3class=Ta5d6ff"Ta5d6ffblock w-full pl-10 pr-3 py-2 border border-gray-300 dark:border-zinc-600 rounded-md leading-5 bg-white dark:bg-zinc-900 text-gray-900 dark:text-white placeholder-gray-500 focus:outline-hidden focus:ring-1 focus:ring-blue-500 focus:border-blue-500 sm:text-smTa5d6ff"
Te6edf3:placeholder=Ta5d6ff"Te6edf3$tTa5d6ff(Ta5d6ff'Ta5d6ffdebug.search_logs_placeholderTa5d6ff'Ta5d6ff)Ta5d6ff"
@Te6edf3input=Ta5d6ff"Ta5d6ffdebouncedSearchTa5d6ff"
Tff7b72/>
Tff7b72</div>
Tff7b72<select
Te6edf3v-model=Ta5d6ff"Ta5d6fflevelTa5d6ff"
Te6edf3class=Ta5d6ff"Ta5d6ffblock pl-3 pr-10 py-2 text-base border-gray-300 dark:border-zinc-600 focus:outline-hidden focus:ring-blue-500 focus:border-blue-500 sm:text-sm rounded-md bg-white dark:bg-zinc-900 text-gray-900 dark:text-whiteTa5d6ff"
@Te6edf3change=Ta5d6ff"Ta5d6ffrefreshLogsTa5d6ff"
Tff7b72>
Tff7b72<option Te6edf3value=Ta5d6ff"Ta5d6ff"Tff7b72>{{ Te6edf3$t("debug.level_all") }}Tff7b72</option>
Tff7b72<option Te6edf3value=Ta5d6ff"Ta5d6ffDEBUGTa5d6ff"Tff7b72>DebugTff7b72</option>
Tff7b72<option Te6edf3value=Ta5d6ff"Ta5d6ffINFOTa5d6ff"Tff7b72>InfoTff7b72</option>
Tff7b72<option Te6edf3value=Ta5d6ff"Ta5d6ffWARNINGTa5d6ff"Tff7b72>WarningTff7b72</option>
Tff7b72<option Te6edf3value=Ta5d6ff"Ta5d6ffERRORTa5d6ff"Tff7b72>ErrorTff7b72</option>
Tff7b72<option Te6edf3value=Ta5d6ff"Ta5d6ffCRITICALTa5d6ff"Tff7b72>CriticalTff7b72</option>
Tff7b72</select>
Tff7b72<label Te6edf3class=Ta5d6ff"Ta5d6ffinline-flex items-center cursor-pointerTa5d6ff"Tff7b72>
Tff7b72<input
Te6edf3v-model=Ta5d6ff"Ta5d6ffis_anomalyTa5d6ff"
Te6edf3type=Ta5d6ff"Ta5d6ffcheckboxTa5d6ff"
Te6edf3class=Ta5d6ff"Ta5d6ffform-checkbox h-4 w-4 text-blue-600 transition duration-150 ease-in-outTa5d6ff"
@Te6edf3change=Ta5d6ff"Ta5d6ffrefreshLogsTa5d6ff"
Tff7b72/>
Tff7b72<span Te6edf3class=Ta5d6ff"Ta5d6ffml-2 text-sm text-gray-700 dark:text-gray-300Ta5d6ff"Tff7b72>{{
Te6edf3$t("debug.anomalies_only")
}}Tff7b72</span>
Tff7b72</label>
Tff7b72</div>
Tff7b72<div
v-else
Te6edf3class=Ta5d6ff"Ta5d6ffflex flex-wrap gap-3 items-center bg-white/50 dark:bg-zinc-800/50 p-3 rounded-lg border border-gray-200 dark:border-zinc-700Ta5d6ff"
Tff7b72>
Tff7b72<div Te6edf3class=Ta5d6ff"Ta5d6ffrelative flex-1 min-w-[200px]Ta5d6ff"Tff7b72>
Tff7b72<div Te6edf3class=Ta5d6ff"Ta5d6ffabsolute inset-y-0 left-0 pl-3 flex items-center pointer-events-noneTa5d6ff"Tff7b72>
Tff7b72<MaterialDesignIcon Te6edf3icon-name=Ta5d6ff"Ta5d6ffmagnifyTa5d6ff" Te6edf3class=Ta5d6ff"Ta5d6ffw-4 h-4 text-gray-400Ta5d6ff" Tff7b72/>
Tff7b72</div>
Tff7b72<input
Te6edf3v-model=Ta5d6ff"Ta5d6ffaccessSearchTa5d6ff"
Te6edf3type=Ta5d6ff"Ta5d6fftextTa5d6ff"
Te6edf3class=Ta5d6ff"Ta5d6ffblock w-full pl-10 pr-3 py-2 border border-gray-300 dark:border-zinc-600 rounded-md leading-5 bg-white dark:bg-zinc-900 text-gray-900 dark:text-white placeholder-gray-500 focus:outline-hidden focus:ring-1 focus:ring-blue-500 focus:border-blue-500 sm:text-smTa5d6ff"
Te6edf3:placeholder=Ta5d6ff"Te6edf3$tTa5d6ff(Ta5d6ff'Ta5d6ffdebug.search_access_placeholderTa5d6ff'Ta5d6ff)Ta5d6ff"
@Te6edf3input=Ta5d6ff"Ta5d6ffdebouncedAccessSearchTa5d6ff"
Tff7b72/>
Tff7b72</div>
Tff7b72<select
Te6edf3v-model=Ta5d6ff"Ta5d6ffaccessOutcomeTa5d6ff"
Te6edf3class=Ta5d6ff"Ta5d6ffblock pl-3 pr-10 py-2 text-base border-gray-300 dark:border-zinc-600 focus:outline-hidden focus:ring-blue-500 focus:border-blue-500 sm:text-sm rounded-md bg-white dark:bg-zinc-900 text-gray-900 dark:text-whiteTa5d6ff"
@Te6edf3change=Ta5d6ff"Ta5d6ffrefreshAccessAttemptsTa5d6ff"
Tff7b72>
Tff7b72<option Te6edf3value=Ta5d6ff"Ta5d6ff"Tff7b72>{{ Te6edf3$t("debug.outcome_all") }}Tff7b72</option>
Tff7b72<option Te6edf3value=Ta5d6ff"Ta5d6ffsuccessTa5d6ff"Tff7b72>successTff7b72</option>
Tff7b72<option Te6edf3value=Ta5d6ff"Ta5d6fffailed_passwordTa5d6ff"Tff7b72>failed_passwordTff7b72</option>
Tff7b72<option Te6edf3value=Ta5d6ff"Ta5d6ffrate_limitedTa5d6ff"Tff7b72>rate_limitedTff7b72</option>
Tff7b72<option Te6edf3value=Ta5d6ff"Ta5d6fflockoutTa5d6ff"Tff7b72>lockoutTff7b72</option>
Tff7b72<option Te6edf3value=Ta5d6ff"Ta5d6ffinvalid_jsonTa5d6ff"Tff7b72>invalid_jsonTff7b72</option>
Tff7b72<option Te6edf3value=Ta5d6ff"Ta5d6ffauth_not_setupTa5d6ff"Tff7b72>auth_not_setupTff7b72</option>
Tff7b72<option Te6edf3value=Ta5d6ff"Ta5d6ffpassword_requiredTa5d6ff"Tff7b72>password_requiredTff7b72</option>
Tff7b72<option Te6edf3value=Ta5d6ff"Ta5d6ffweak_passwordTa5d6ff"Tff7b72>weak_passwordTff7b72</option>
Tff7b72<option Te6edf3value=Ta5d6ff"Ta5d6ffsetup_already_doneTa5d6ff"Tff7b72>setup_already_doneTff7b72</option>
Tff7b72</select>
Tff7b72</div>
Tff7b72</div>
Tff7b72<div
Te6edf3class=Ta5d6ff"Ta5d6ffflex-1 overflow-hidden glass-card max-w-6xl mx-auto w-full p-0 flex flex-col rounded-xs min-h-0Ta5d6ff"
Tff7b72>
Tff7b72<div
Te6edf3v-if=Ta5d6ff"Ta5d6ffactiveTab === Ta5d6ff'Ta5d6fflogsTa5d6ff'Ta5d6ff"
Te6edf3class=Ta5d6ff"Ta5d6ffdebug-log-scroll flex-1 overflow-auto p-2 sm:p-4 font-mono text-[9px] sm:text-[10px] md:text-xs max-sm:leading-snug sm:leading-snug md:leading-relaxed select-text touch-pan-x bg-white dark:bg-zinc-950 min-h-0Ta5d6ff"
Tff7b72>
Tff7b72<div Te6edf3v-if=Ta5d6ff"Ta5d6fflogs.length === 0Ta5d6ff" Te6edf3class=Ta5d6ff"Ta5d6fftext-gray-500 italic text-center py-10 text-sm sm:text-baseTa5d6ff"Tff7b72>
{{ loading ? Te6edf3$t("debug.loading_logs") : Te6edf3$t("debug.no_logs") }}
Tff7b72</div>
Tff7b72<div
Te6edf3v-for=Ta5d6ff"Ta5d6ff(log, index) in logsTa5d6ff"
Te6edf3:key=Ta5d6ff"Ta5d6ffindexTa5d6ff"
Te6edf3class=Ta5d6ff"Ta5d6ffdebug-log-row border-b border-gray-100 dark:border-zinc-900 py-1 sm:py-1.5 flex gap-1.5 sm:gap-3 max-sm:flex-nowrap max-sm:min-w-max hover:bg-gray-50 dark:hover:bg-zinc-900/50 cursor-copyTa5d6ff"
Te6edf3:class=Ta5d6ff"Ta5d6ff{ Ta5d6ff'Ta5d6ffbg-red-50/30 dark:bg-red-900/10Ta5d6ff'Ta5d6ff: log.is_anomaly }Ta5d6ff"
Te6edf3title=Ta5d6ff"Ta5d6ffTap to copy this log entryTa5d6ff"
@Te6edf3click=Ta5d6ff"Ta5d6ffcopyLogLine(log)Ta5d6ff"
Tff7b72>
Tff7b72<span Te6edf3class=Ta5d6ff"Ta5d6fftext-gray-400 shrink-0 max-sm:text-[8px]Ta5d6ff"Tff7b72>{{ formatTime(log.timestamp) }}Tff7b72</span>
Tff7b72<span
Te6edf3:class=Ta5d6ff"Ta5d6fflevelClass(log.level)Ta5d6ff"
Te6edf3class=Ta5d6ff"Ta5d6ffw-11 sm:w-12 shrink-0 font-bold uppercase max-sm:text-[8px] max-sm:tracking-tightTa5d6ff"
Tff7b72>{{ log.level }}Tff7b72</span
>
Tff7b72<span
Te6edf3class=Ta5d6ff"Ta5d6fftext-blue-500 shrink-0 w-[4.5rem] sm:w-24 overflow-hidden text-ellipsis italic max-sm:text-[8px]Ta5d6ff"
Tff7b72>[{{ log.module }}]Tff7b72</span
>
Tff7b72<span
Te6edf3class=Ta5d6ff"Ta5d6fftext-gray-800 dark:text-gray-200 flex-1 max-sm:whitespace-nowrap sm:wrap-break-wordTa5d6ff"
Tff7b72>
{{ log.message }}
Tff7b72<span
Te6edf3v-if=Ta5d6ff"Ta5d6fflog.is_anomalyTa5d6ff"
Te6edf3class=Ta5d6ff"Ta5d6ffml-2 inline-flex items-center px-1.5 py-0.5 rounded-full text-[8px] font-medium bg-red-100 text-red-800 dark:bg-red-900 dark:text-red-200 uppercaseTa5d6ff"
Tff7b72>
Tff7b72<MaterialDesignIcon Te6edf3icon-name=Ta5d6ff"Ta5d6ffalert-circleTa5d6ff" Te6edf3class=Ta5d6ff"Ta5d6ffw-2.5 h-2.5 mr-1Ta5d6ff" Tff7b72/>
{{ log.anomaly_type || "anomaly" }}
Tff7b72</span>
Tff7b72</span>
Tff7b72</div>
Tff7b72</div>
Tff7b72<div
v-else
Te6edf3class=Ta5d6ff"Ta5d6ffdebug-log-scroll flex-1 overflow-auto p-2 sm:p-4 font-mono text-[9px] sm:text-[10px] md:text-xs max-sm:leading-snug sm:leading-snug md:leading-relaxed select-text touch-pan-x bg-white dark:bg-zinc-950 min-h-0Ta5d6ff"
Tff7b72>
Tff7b72<div
Te6edf3v-if=Ta5d6ff"Ta5d6ffaccessAttempts.length === 0Ta5d6ff"
Te6edf3class=Ta5d6ff"Ta5d6fftext-gray-500 italic text-center py-10 text-sm sm:text-baseTa5d6ff"
Tff7b72>
{{ accessLoading ? Te6edf3$t("debug.loading_access") : Te6edf3$t("debug.no_access") }}
Tff7b72</div>
Tff7b72<div
Te6edf3v-for=Ta5d6ff"Ta5d6ffrow in accessAttemptsTa5d6ff"
Te6edf3:key=Ta5d6ff"Ta5d6ffrow.idTa5d6ff"
Te6edf3class=Ta5d6ff"Ta5d6ffborder-b border-gray-100 dark:border-zinc-900 py-1.5 sm:py-2 flex flex-col gap-0.5 sm:gap-1 hover:bg-gray-50 dark:hover:bg-zinc-900/50 cursor-copyTa5d6ff"
Te6edf3title=Ta5d6ff"Ta5d6ffTap to copy this access entryTa5d6ff"
@Te6edf3click=Ta5d6ff"Ta5d6ffcopyAccessLine(row)Ta5d6ff"
Tff7b72>
Tff7b72<div
Te6edf3class=Ta5d6ff"Ta5d6ffflex flex-wrap gap-x-2 gap-y-0.5 sm:gap-x-3 sm:gap-y-1 items-center max-sm:text-[9px]Ta5d6ff"
Tff7b72>
Tff7b72<span Te6edf3class=Ta5d6ff"Ta5d6fftext-gray-400 shrink-0Ta5d6ff"Tff7b72>{{ formatTime(row.created_at) }}Tff7b72</span>
Tff7b72<span Te6edf3class=Ta5d6ff"Ta5d6fftext-amber-600 dark:text-amber-400 font-semiboldTa5d6ff"Tff7b72>{{ row.outcome }}Tff7b72</span>
Tff7b72<span Te6edf3class=Ta5d6ff"Ta5d6fftext-cyan-600 dark:text-cyan-400 max-sm:break-all sm:min-w-0Ta5d6ff"
Tff7b72>{{ row.method }} {{ row.path }}Tff7b72</span
>
Tff7b72</div>
Tff7b72<div Te6edf3class=Ta5d6ff"Ta5d6fftext-gray-600 dark:text-gray-400 break-all pl-0 max-sm:text-[8px]Ta5d6ff"Tff7b72>
Tff7b72<span Te6edf3class=Ta5d6ff"Ta5d6fftext-gray-500Ta5d6ff"Tff7b72>IPTff7b72</span> {{ row.client_ip }}
Tff7b72</div>
Tff7b72<div Te6edf3class=Ta5d6ff"Ta5d6fftext-gray-600 dark:text-gray-400 break-all max-sm:text-[8px]Ta5d6ff"Tff7b72>
Tff7b72<span Te6edf3class=Ta5d6ff"Ta5d6fftext-gray-500Ta5d6ff"Tff7b72>UATff7b72</span> {{ row.user_agent || "-" }}
Tff7b72</div>
Tff7b72<div Te6edf3v-if=Ta5d6ff"Ta5d6ffrow.detailTa5d6ff" Te6edf3class=Ta5d6ff"Ta5d6fftext-gray-500 max-sm:text-[8px] sm:text-[10px]Ta5d6ff"Tff7b72>
{{ row.detail }}
Tff7b72</div>
Tff7b72</div>
Tff7b72</div>
Tff7b72<div
Te6edf3class=Ta5d6ff"Ta5d6ffpx-4 py-3 flex items-center justify-between border-t border-gray-200 dark:border-zinc-800 bg-gray-50 dark:bg-zinc-900/50Ta5d6ff"
Tff7b72>
Tff7b72<div Te6edf3class=Ta5d6ff"Ta5d6ffflex-1 flex justify-between sm:hiddenTa5d6ff"Tff7b72>
Tff7b72<button
Te6edf3class=Ta5d6ff"Ta5d6ffrelative inline-flex items-center px-4 py-2 border border-gray-300 text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50 disabled:opacity-50Ta5d6ff"
Te6edf3:disabled=Ta5d6ff"Ta5d6fflistOffset === 0Ta5d6ff"
@Te6edf3click=Ta5d6ff"Ta5d6ffprevPageTa5d6ff"
Tff7b72>
Previous
Tff7b72</button>
Tff7b72<button
Te6edf3class=Ta5d6ff"Ta5d6ffml-3 relative inline-flex items-center px-4 py-2 border border-gray-300 text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50 disabled:opacity-50Ta5d6ff"
Te6edf3:disabled=Ta5d6ff"Ta5d6fflistOffset + limit >= listTotalTa5d6ff"
@Te6edf3click=Ta5d6ff"Ta5d6ffnextPageTa5d6ff"
Tff7b72>
Next
Tff7b72</button>
Tff7b72</div>
Tff7b72<div Te6edf3class=Ta5d6ff"Ta5d6ffhidden sm:flex-1 sm:flex sm:items-center sm:justify-betweenTa5d6ff"Tff7b72>
Tff7b72<divTff7b72>
Tff7b72<p Te6edf3class=Ta5d6ff"Ta5d6fftext-sm text-gray-700 dark:text-gray-400 font-monoTa5d6ff"Tff7b72>
Showing
Tff7b72<span Te6edf3class=Ta5d6ff"Ta5d6fffont-boldTa5d6ff"Tff7b72>{{ listTotal === 0 ? 0 : listOffset + 1 }}Tff7b72</span>
to
Tff7b72<span Te6edf3class=Ta5d6ff"Ta5d6fffont-boldTa5d6ff"Tff7b72>{{ Math.min(listOffset + limit, listTotal) }}Tff7b72</span>
of
Tff7b72<span Te6edf3class=Ta5d6ff"Ta5d6fffont-boldTa5d6ff"Tff7b72>{{ listTotal }}Tff7b72</span>
results
Tff7b72</p>
Tff7b72</div>
Tff7b72<div Te6edf3class=Ta5d6ff"Ta5d6ffflex gap-2Ta5d6ff"Tff7b72>
Tff7b72<button
Te6edf3class=Ta5d6ff"Ta5d6ffsecondary-chip px-3 py-1 text-xs disabled:opacity-50Ta5d6ff"
Te6edf3:disabled=Ta5d6ff"Ta5d6fflistOffset === 0Ta5d6ff"
@Te6edf3click=Ta5d6ff"Ta5d6ffprevPageTa5d6ff"
Tff7b72>
Tff7b72<MaterialDesignIcon Te6edf3icon-name=Ta5d6ff"Ta5d6ffchevron-leftTa5d6ff" Te6edf3class=Ta5d6ff"Ta5d6ffw-4 h-4Ta5d6ff" Tff7b72/>
Previous
Tff7b72</button>
Tff7b72<button
Te6edf3class=Ta5d6ff"Ta5d6ffsecondary-chip px-3 py-1 text-xs disabled:opacity-50Ta5d6ff"
Te6edf3:disabled=Ta5d6ff"Ta5d6fflistOffset + limit >= listTotalTa5d6ff"
@Te6edf3click=Ta5d6ff"Ta5d6ffnextPageTa5d6ff"
Tff7b72>
Next
Tff7b72<MaterialDesignIcon Te6edf3icon-name=Ta5d6ff"Ta5d6ffchevron-rightTa5d6ff" Te6edf3class=Ta5d6ff"Ta5d6ffw-4 h-4Ta5d6ff" Tff7b72/>
Tff7b72</button>
Tff7b72</div>
Tff7b72</div>
Tff7b72</div>
Tff7b72</div>
Tff7b72</div>
Tff7b72</div>
Tff7b72</template>
Tff7b72<scriptTff7b72>
import MaterialDesignIcon from "../MaterialDesignIcon.vue";
import ToastUtils from "../../js/ToastUtils";
import ToolsPageHeader from "../tools/ToolsPageHeader.vue";
export default {
name: "DebugLogsPage",
components: {
MaterialDesignIcon,
ToolsPageHeader,
},
data() {
return {
activeTab: "logs",
logs: [],
total: 0,
limit: 100,
offset: 0,
search: "",
level: "",
is_anomaly: false,
loading: false,
updateInterval: null,
searchTimeout: null,
accessAttempts: [],
accessTotal: 0,
accessOffset: 0,
accessSearch: "",
accessOutcome: "",
accessLoading: false,
accessSearchTimeout: null,
};
},
computed: {
listOffset() {
return this.activeTab === "logs" ? this.offset : this.accessOffset;
},
listTotal() {
return this.activeTab === "logs" ? this.total : this.accessTotal;
},
},
mounted() {
this.refreshLogs();
this.updateInterval = setInterval(() => {
if (this.activeTab !== "logs") return;
if (this.offset === 0 && !this.search && !this.is_anomaly && !this.level) {
this.refreshLogs(true);
}
}, 5000);
},
beforeUnmount() {
if (this.updateInterval) {
clearInterval(this.updateInterval);
}
if (this.searchTimeout) {
clearTimeout(this.searchTimeout);
}
if (this.accessSearchTimeout) {
clearTimeout(this.accessSearchTimeout);
}
},
methods: {
switchTab(tab) {
this.activeTab = tab;
if (tab === "access" && this.accessAttempts.length === 0 && !this.accessLoading) {
this.refreshAccessAttempts();
}
},
refreshActive() {
if (this.activeTab === "logs") this.refreshLogs();
else this.refreshAccessAttempts();
},
async refreshLogs(silent = false) {
if (!silent) this.loading = true;
try {
const params = {
limit: this.limit,
offset: this.offset,
search: this.search || undefined,
level: this.level || undefined,
is_anomaly: this.is_anomaly ? true : undefined,
};
const response = await window.api.get("/api/v1/debug/logs", { params });
this.logs = response.data.logs;
this.total = response.data.total;
} catch (e) {
console.log("Failed to fetch logs", e);
if (!silent) ToastUtils.error(this.$t("debug.failed_fetch_logs"));
} finally {
if (!silent) this.loading = false;
}
},
async refreshAccessAttempts(silent = false) {
if (!silent) this.accessLoading = true;
try {
const params = {
limit: this.limit,
offset: this.accessOffset,
search: this.accessSearch || undefined,
outcome: this.accessOutcome || undefined,
};
const response = await window.api.get("/api/v1/debug/access-attempts", { params });
this.accessAttempts = response.data.attempts;
this.accessTotal = response.data.total;
} catch (e) {
console.log("Failed to fetch access attempts", e);
if (!silent) ToastUtils.error(this.$t("debug.failed_fetch_access"));
} finally {
if (!silent) this.accessLoading = false;
}
},
debouncedSearch() {
if (this.searchTimeout) clearTimeout(this.searchTimeout);
this.searchTimeout = setTimeout(() => {
this.offset = 0;
this.refreshLogs();
}, 500);
},
debouncedAccessSearch() {
if (this.accessSearchTimeout) clearTimeout(this.accessSearchTimeout);
this.accessSearchTimeout = setTimeout(() => {
this.accessOffset = 0;
this.refreshAccessAttempts();
}, 500);
},
prevPage() {
if (this.activeTab === "logs") {
if (this.offset >= this.limit) {
this.offset -= this.limit;
this.refreshLogs();
}
} else if (this.accessOffset >= this.limit) {
this.accessOffset -= this.limit;
this.refreshAccessAttempts();
}
},
nextPage() {
if (this.activeTab === "logs") {
if (this.offset + this.limit Tff7b72< this.total) {
this.offset += this.limit;
this.refreshLogs();
}
} else if (this.accessOffset + this.limit < this.accessTotal) {
this.accessOffset += this.limit;
this.refreshAccessAttempts();
}
},
formatTime(timestamp) {
try {
const Te6edf3ts = Ta5d6fftypeof Te6edf3timestamp =Ta5d6ff== "number" ? timestamp * 1000 : timestamp;
const Te6edf3date = Ta5d6ffnew Date(ts);
return date.toLocaleString();
} catch {
return timestamp;
}
},
levelClass(level) {
const Te6edf3l = Ta5d6fflevel.toUpperCase();
if (Te6edf3l =Ta5d6ff== "ERROR" || Te6edf3l =Ta5d6ff== "CRITICAL") return "text-red-500";
if (Te6edf3l =Ta5d6ff== "WARNING") return "text-orange-500";
if (Te6edf3l =Ta5d6ff== "INFO") return "text-blue-500";
if (Te6edf3l =Ta5d6ff== "DEBUG") return "text-gray-500";
return "text-gray-400";
},
async copyActive() {
if (Te6edf3this.activeTab =Ta5d6ff== "logs") {
const Te6edf3logText = Ta5d6ffthis.logs
.map(
(l) =Tff7b72>
`${this.formatTime(l.timestamp)} [${l.level}] [${l.module}] ${l.message}${l.is_anomaly ? " [ANOMALY:" + l.anomaly_type + "]" : ""}`
)
.join("\n");
try {
await navigator.clipboard.writeText(logText);
ToastUtils.success(this.$t("debug.logs_copied"));
} catch {
ToastUtils.error(this.$t("debug.failed_copy_logs"));
}
} else {
const lines = this.accessAttempts.map((r) =>
[
this.formatTime(r.created_at),
r.outcome,
r.method,
r.path,
r.client_ip,
r.user_agent || "",
r.detail || "",
].join(" | ")
);
try {
await navigator.clipboard.writeText(lines.join("\n"));
ToastUtils.success(this.$t("debug.access_copied"));
} catch {
ToastUtils.error(this.$t("debug.failed_copy_access"));
}
}
},
async copyLogLine(log) {
const line = `${this.formatTime(log.timestamp)} [${log.level}] [${log.module}] ${log.message}${log.is_anomaly ? " [ANOMALY:" + (log.anomaly_type || "unknown") + "]" : ""}`;
try {
await navigator.clipboard.writeText(line);
ToastUtils.success(this.$t("debug.logs_copied"));
} catch {
ToastUtils.error(this.$t("debug.failed_copy_logs"));
}
},
async copyAccessLine(row) {
const line = [
this.formatTime(row.created_at),
row.outcome,
row.method,
row.path,
row.client_ip,
row.user_agent || "",
row.detail || "",
].join(" | ");
try {
await navigator.clipboard.writeText(line);
ToastUtils.success(this.$t("debug.access_copied"));
} catch {
ToastUtils.error(this.$t("debug.failed_copy_access"));
}
},
},
};
Tff7b72</script>
Tff7b72<style scopedTff7b72>
.glass-card {
border-radius: 2px !important;
}
.debug-log-scroll {
-webkit-text-size-adjust: 100%;
text-size-adjust: 100%;
}
Tff7b72</style>
Served by rngit 1.5.2 - Generated in 0.05s